/** @file cache.c
 *  @brief The cache workload.
 *  
 *  Define the interface that allows the workload generator to stress the cache.
 */
#include <stdlib.h>
#include "cJSON.h"
#include "memory.h"
#include "cache.h"
#include "generator.h"
#include "scheduler.h"

/** Sets up a cache buffer from a resource object represented in JSON */
memory_generator_state *setup_cache_buffer(cJSON *, char *);

/** Holds all the modes for all the cache resources. */
mode_definition_list *cache_modes[HASHSIZE];

/** Set up the L1 Cache's Write Mode. */
mode *setup_l1_cache_write(cJSON *);
/** Set up the L2 Cache's Write Mode. */
mode *setup_l2_cache_write(cJSON *);

/** Write to the L1 cache. */
void * l1_cache_write(void *);
/** Write to the L2 cache. */
void * l2_cache_write(void *);

/** Spawn an L1 Cache Job and Schedule the Next Job */
void create_l1_cache_workload(void *);

/** Spawn an L2 Cache Job and Schedule the Next Job */
void create_l2_cache_workload(void *);

resource_model *setup_l1_cache(cJSON *json) {
  memory_generator_state *state;
  install_mode(cache_modes, "l1_w", setup_l1_cache_write);
  resource_model *model = malloc(sizeof(resource_model));
  setup_rng("l1_cache_arrival", cJSON_GetObjectItem(json, "distribution"));
  state = setup_cache_buffer(json, "random_position_in_l1");
  model->birth = create_l1_cache_workload;
  model->state = (void*)state;
  model->modes = initialize_modes(cache_modes, json);
  model->cnt_modes = count_modes(json);
  return model;
}

resource_model *setup_l2_cache(cJSON *json) {
  memory_generator_state *state;
  install_mode(cache_modes, "l2_w", setup_l2_cache_write);
  resource_model *model = malloc(sizeof(resource_model));
  setup_rng("l2_cache_arrival", cJSON_GetObjectItem(json,"distribution"));
  state = setup_cache_buffer(json,"random_position_in_l2");
  model->birth = create_l2_cache_workload;
  model->state = (void*)state;
  model->modes = initialize_modes(cache_modes, json);
  model->cnt_modes = count_modes(json);
  return model;
}

memory_generator_state *setup_cache_buffer(cJSON *json, char *rng_name){
  cJSON *cache_size = cJSON_GetObjectItem(json,"cache_size");
  unsigned int size = cache_size->valueint;
  return initialize_memory(size,GEN_SEQ); //Only write to cache sequentially for now.
}

void create_l1_cache_workload(void *args) {
  free( spawn_job((resource_model *) args) );
  schedule_event(ms_from_now(get_random_number("l1_cache_arrival")), create_l1_cache_workload, args);
}

void create_l2_cache_workload(void *args) {
  free( spawn_job((resource_model *) args) );
  schedule_event(ms_from_now(get_random_number("l2_cache_arrival")), create_l2_cache_workload, args);
}

void *l1_cache_write(void *args) {
#if 0 //benchmarking
  struct timespec result = {0};
  struct timespec fp = {0};
  struct timespec pp = {0};
  clock_gettime(CLOCK_MONOTONIC,&fp);
#endif
  memory_generator_state  *state = (memory_generator_state *)args;
  unsigned long position = (unsigned long) get_random_number("random_position_in_l1");
  unsigned int bytes_to_write = (unsigned long) get_random_number("l1_cache_w_length");
  memory_write_bytes(&state->sequential,bytes_to_write);
#if 0 //calculate benchmark
  clock_gettime(CLOCK_MONOTONIC,&pp);
  result = diff(&fp,&pp);
  printf("Seconds:%d, Nanoseconds:%d\n",result.tv_sec,result.tv_nsec);
#endif
}

void *l2_cache_write(void *args) {
  memory_generator_state  *state = (memory_generator_state *)args;
  unsigned long position = (unsigned long) get_random_number("random_position_in_l2");
  unsigned int bytes_to_write = (unsigned long) get_random_number("l2_cache_w_length");
  memory_write_bytes(&state->sequential, bytes_to_write);
}

mode *setup_l1_cache_write(cJSON *json) {
  setup_rng("l1_cache_w_length", cJSON_GetObjectItem(json,"length"));
  return new_mode("l1_w",l1_cache_write,json);
}

mode *setup_l2_cache_write(cJSON *json) {
  setup_rng("l2_cache_w_length", cJSON_GetObjectItem(json,"length"));
  return new_mode("l2_w", l2_cache_write, json);
}
